package de.medieninf.sensimg;

import javax.microedition.lcdui.Canvas;

/**
 * Represents current state of pressed keys. Information
 * stored here will be sent down to the server
 * @author pb
 */
public class KeyboardState {
    /**
     * Maximal simultaneously pressed keys.
     */
    private static final int MAX_PRESSED = 4;

    /**
     * Currently pressed keys in readable form (char).
     */
    private char[] pressed;

    /**
     * Next free index where a newly pressed key can be inserted.
     */
    private int nextFree;

    /**
     * A lock object guarding pressed.
     */
    private Object lock;

    /**
     * All characters that are allowed in the form they will
     * be sent down.
     */
    private static final int[] ALLOWED_CHARS = {
        'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
        'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
        's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
        '0', '1', '2', '3', '4', '5', '6', '7',
        '8', '9', '*', '#',
    };

    /**
     * New empty KeyboardState instance.
     */
    public KeyboardState() {
        pressed = new char[MAX_PRESSED];
        nextFree = 0;
        lock = new Object();
    }

    /**
     * Checks whether ch is one of chars.
     * @param ch int, char to test
     * @param chars int[], possible chars
     * @return true iff ch is one of chars
     */
    private static boolean charIn(final int ch, final int[] chars) {
        for (int i = 0; i < chars.length; i++) {
            if (ch == chars[i]) {
                return true;
            }
        }
        return false;
    }

    /**
     * Converts keyCode to readable character (int).
     * @param ch int, keyCode
     * @param gch int, gameActionCode
     * @return readable character
     */
    private int keyConvert(final int ch, final int gch) {
        switch (gch) {
        case Canvas.LEFT:
            return 'l';
        case Canvas.RIGHT:
            return 'r';
        case Canvas.UP:
            return 'u';
        case Canvas.DOWN:
            return 'd';
        case Canvas.FIRE:
            return 'f';
        case Canvas.KEY_NUM0:
            return '0';
        case Canvas.KEY_NUM1:
            return '1';
        case Canvas.KEY_NUM2:
            return '2';
        case Canvas.KEY_NUM3:
            return '3';
        case Canvas.KEY_NUM4:
            return '4';
        case Canvas.KEY_NUM5:
            return '5';
        case Canvas.KEY_NUM6:
            return '6';
        case Canvas.KEY_NUM7:
            return '7';
        case Canvas.KEY_NUM8:
            return '8';
        case Canvas.KEY_NUM9:
            return '9';
        default:
            if (charIn(ch, ALLOWED_CHARS)) {
                // Logger.log("Key accepted " + ch + " " + ((char) (ch)));
                return ch;
            } else {
                Logger.log("Key ignored " + ch + " " + ((char) (ch)));
            }
        }
        return ch;
    }

    /**
     * Connect keyPress, call from Canvas implementations.
     * @param ch keyCode read
     * @param gch gameKeyCode, keyCode run through getGameAction
     * @return true iff key was accepted
     */
    public final boolean onKeyDown(final int ch, final int gch) {
        if (ch == 0) {
            return false;
        }
        return onKeyDown(keyConvert(ch, gch));
    }

    /**
     * Connect keyPress, call if key is already known.
     * @param ch character read
     * @return true iff key was accepted
     */
    public final boolean onKeyDown(final int ch) {
        if (nextFree < MAX_PRESSED) {
            synchronized (lock) {
                for (int i = 0; i < nextFree; i++) {
                    if (pressed[i] == ch) {
                        return false; // ignore already pressed (key repeat)
                    }
                }
                pressed[nextFree++] = (char) ch;
            }
            return true;
        }
        return false;
    }

    /**
     * Connect keyRelease, call from Canvas implementations.
     * @param ch keyCode read
     * @param gch gameKeyCode, keyCode run through getGameAction
     * @return true iff key was accepted
     */
    public final boolean onKeyUp(final int ch, final int gch) {
        if (ch == 0) {
            return false;
        }
        return onKeyUp(keyConvert(ch, gch));
    }

    /**
     * Connect keyRelease, call if key is already known.
     * @param ch character read
     * @return true iff key was accepted
     */
    public final boolean onKeyUp(final int ch) {
        synchronized (lock) {
            for (int i = 0; i < nextFree; i++) {
                if (pressed[i] == ch) {
                    pressed[i] = pressed[--nextFree];
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Which keys are currently pressed, for sending down.
     * @return String with currently pressed keys
     */
    public final String getKeys() {
        char[] data;

        synchronized (lock) {
            data = new char[nextFree];
            for (int i = 0; i < data.length; i++) {
                data[i] = pressed[i];
            }
        }
        String ret = new String(data);
        // System.out.println("keys are " + ret);
        return ret;
    }

}
